#include <utility_math.h>
#include <vec3.h>
#include <cmath>

std::tuple<float, float, float> 
barycentric(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec2f& target) {
    Vec2f tp1 = Vec2f(p1) - target;
    Vec2f tp2 = Vec2f(p2) - target;
    Vec2f tp3 = Vec2f(p3) - target;

    float s1 = cross(tp2, tp3);
    float s2 = cross(tp3, tp1);
    float s3 = cross(tp1, tp2);

    float alpha = s1 / (s1 + s2 + s3);
    float beta  = s2 / (s1 + s2 + s3);
    float gamma = 1 - alpha - beta;

    return { alpha, beta, gamma };
}

bool isInRect(const Vec3f& p, const Bound2i& rect) {
    return p.x <= rect.pMax.x && p.y <= rect.pMax.y &&
           p.x >= rect.pMin.x && p.y >= rect.pMin.y;
}

bool isInTriangle(const Vec2f p1, const Vec2f p2, const Vec2f p3, const Vec2f target) {
    Vec2f tp1 = p1 - target;
    Vec2f tp2 = p2 - target;
    Vec2f tp3 = p3 - target;

    float sin1 = cross(tp1, tp2);
    float sin2 = cross(tp2, tp3);
    float sin3 = cross(tp3, tp1);

    return (sin1 >= 0 && sin2 >= 0 && sin3 >= 0) || (sin1 <= 0 && sin2 <= 0 && sin3 <= 0);
}

bool needClip(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Bound2i& screen) {
    return isInRect(p1, screen) || isInRect(p2, screen) || isInRect(p3, screen);
}

Bound2i AABBbox(const Vec2f& p1, const Vec2f& p2, const Vec2f& p3) {
    int xMin = (int)std::min({p1.x, p2.x, p3.x});
    int xMax = (int)std::max({p1.x, p2.x, p3.x}) + 1;
    int yMin = (int)std::min({p1.y, p2.y, p3.y});
    int yMax = (int)std::max({p1.y, p2.y, p3.y}) + 1;

    return Bound2i{Vec2i{xMin, yMin}, Vec2i{xMax, yMax}};
}

std::vector<std::pair<int, int>> 
DDALine(const Vec2f& p1, const Vec2f& p2) {
    int dx = (int)(p2.x - p1.x);
    int dy = (int)(p2.y - p1.y);
    int N = std::max(::abs(dx), ::abs(dy));
    
    float divN = N == 0 ? 0.0f : 1.0f / N;
    float stepx = dx * divN;
    float stepy = dy * divN;
    std::vector<std::pair<int, int>> line; 
    float x = p1.x;
    float y = p1.y;
    for (int step = 0; step < N; ++step, x += stepx, y += stepy) {
        line.push_back({(int)x, (int)y});
    }

    return line;
}

std::vector<std::tuple<Vertex, Vertex, Vertex>>
clipping(const Vertex& p1, const Vertex& p2, const Vertex& p3) {
    std::vector<std::tuple<Vertex, Vertex, Vertex>> primitive;
    if (isInNDC(p1.position) && isInNDC(p2.position) && isInNDC(p3.position)) {
        primitive.push_back({p1, p2, p3});
    }
    return primitive;
}

bool backCulling(const std::tuple<Vertex, Vertex, Vertex>& triangle, const Vec3f& lookDir) {
    Vec3f v1 = std::get<0>(triangle).position - std::get<1>(triangle).position;
    Vec3f v2 = std::get<1>(triangle).position - std::get<2>(triangle).position;
    Vec3f normal = ::cross(v1, v2);

    return normal * lookDir >= 0.0f;
}

bool isInNDC(const Vec3f& p1) {
    return p1.x < 1.0f && p1.x > -1.0f &&
           p1.y < 1.0f && p1.y > -1.0f &&
           p1.z < 1.0f && p1.z > -1.0f;
}

